package info.hexin.quartz.spring;

import info.hexin.quartz.annotation.CronScheduled;
import info.hexin.quartz.annotation.ScheduleLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;

@Component
public class CronScheduledAnnoationBeanPostProcessor implements BeanPostProcessor, Ordered,
        EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
        SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {

    protected final Logger logger = LoggerFactory.getLogger(getClass());
    private final CronScheduledTaskRegistrar registrar = new CronScheduledTaskRegistrar();
    private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
    public Map<String, Object> objectmap = new HashMap<>();
    private Object scheduler;
    private StringValueResolver embeddedValueResolver;
    private BeanFactory beanFactory;
    private ApplicationContext applicationContext;

    @Autowired
    private ScheduleLock scheduleLock;
    private boolean isinit = true;
    private boolean isstart = false;

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    /**
     * Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
     * the scheduled methods, or a {@link ScheduledExecutorService}
     * to be wrapped as a TaskScheduler.
     */
    public void setScheduler(Object scheduler) {
        this.scheduler = scheduler;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.embeddedValueResolver = resolver;
    }

    /**
     * Making a {@link BeanFactory} available is optional; if not set,
     * {@link org.springframework.scheduling.annotation.SchedulingConfigurer} beans won't get autodetected and
     * a {@link #setScheduler scheduler} has to be explicitly configured.
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    /**
     * Setting an {@link ApplicationContext} is optional: If set, registered
     * tasks will be activated in the {@link ContextRefreshedEvent} phase;
     * if not set, it will happen at {@link #afterSingletonsInstantiated} time.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        if (this.beanFactory == null) {
            this.beanFactory = applicationContext;
        }
    }


    @Override
    public void afterSingletonsInstantiated() {
        if (this.applicationContext == null) {
            // Not running in an ApplicationContext -> register tasks early...
            finishRegistration();
        }
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext() == this.applicationContext) {
            // Running in an ApplicationContext -> register tasks this late...
            // giving other ContextRefreshedEvent listeners a chance to perform
            // their work at the same time (e.g. Spring Batch's job registration).
            finishRegistration();
        }
    }

    private void finishRegistration() {
        if (scheduleLock.haslock) {
            reRegister();
        }
    }


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, String beanName) {
        return getObject(bean, beanName);
    }

    public Object getObject(final Object bean, String beanName) {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        if (!this.nonAnnotatedClasses.contains(targetClass)) {
            final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
            ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                    for (CronScheduled scheduled : AnnotationUtils.getRepeatableAnnotation(method, Schedules.class, CronScheduled.class)) {
                        processScheduled(scheduled, method, bean);
                        annotatedMethods.add(method);
                    }
                }
            });
            if (annotatedMethods.isEmpty()) {
                this.nonAnnotatedClasses.add(targetClass);
                if (logger.isDebugEnabled()) {
                    logger.debug("No @CronScheduled annotations found on bean class: " + bean.getClass());
                }
            } else {
                // Non-empty set of methods
                if (logger.isDebugEnabled()) {
                    logger.debug(annotatedMethods.size() + " @CronScheduled methods processed on bean '" + beanName + "': " + annotatedMethods);
                }
            }
        }
        return bean;
    }


    protected void processScheduled(CronScheduled scheduled, Method method, Object bean) {
        try {
            Assert.isTrue(void.class.equals(method.getReturnType()), "Only void-returning methods may be annotated with @CronScheduled");
            Assert.isTrue(method.getParameterTypes().length == 0, "Only no-arg methods may be annotated with @CronScheduled");
            if (isinit)
                objectmap.put(bean.getClass().getName(), bean);
            if (AopUtils.isJdkDynamicProxy(bean)) {
                try {
                    // Found a @CronScheduled method on the target class for this JDK proxy ->
                    // is it also present on the proxy itself?
                    method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                } catch (SecurityException ex) {
                    ReflectionUtils.handleReflectionException(ex);
                } catch (NoSuchMethodException ex) {
                    throw new IllegalStateException(String.format(
                            "@CronScheduled method '%s' found on bean target class '%s' but not " +
                                    "found in any interface(s) for a dynamic proxy. Either pull the " +
                                    "method up to a declared interface or switch to subclass (CGLIB) " +
                                    "proxies by setting proxy-target-class/proxyTargetClass to 'true'",
                            method.getName(), method.getDeclaringClass().getSimpleName()));
                }
            } else if (AopUtils.isCglibProxy(bean)) {
                // Common problem: private methods end up in the proxy instance, not getting delegated.
                if (Modifier.isPrivate(method.getModifiers())) {
                    throw new IllegalStateException(String.format(
                            "@CronScheduled method '%s' found on CGLIB proxy for target class '%s' but cannot " +
                                    "be delegated to target bean. Switch its visibility to package or protected.",
                            method.getName(), method.getDeclaringClass().getSimpleName()));
                }
            }

            Runnable runnable = new ScheduledMethodRunnable(bean, method);
            String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

            // Determine initial delay
            long initialDelay = scheduled.initialDelay();
            String initialDelayString = scheduled.initialDelayString();
            if (StringUtils.hasText(initialDelayString)) {
                Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
                if (this.embeddedValueResolver != null) {
                    initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
                }
                try {
                    initialDelay = Integer.parseInt(initialDelayString);
                } catch (NumberFormatException ex) {
                    throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
                }
            }

            // Check cron expression
            String cron = scheduled.cron();
            String enablestr = scheduled.enable();
            if (StringUtils.isEmpty(cron)) {
                logger.warn("-----------类：" + bean.getClass().getName() + ",方法：" + method.getName() + ",注册为调度任务，无法找见 " + cron + "的Cron配置，会使用Key为Cron表达式");
            } else if (StringUtils.isEmpty(enablestr) && Boolean.parseBoolean(enablestr)) {
                logger.warn("-----------类：" + bean.getClass().getName() + ",方法：" + method.getName() + "注册为调度任务，无法找见 " + enablestr + "的Enable的配置");
            } else
                Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");


            String zone = scheduled.zone();
            if (this.embeddedValueResolver != null) {
                cron = this.embeddedValueResolver.resolveStringValue(cron);
                zone = this.embeddedValueResolver.resolveStringValue(zone);
            }
            TimeZone timeZone;
            if (StringUtils.hasText(zone)) {
                timeZone = StringUtils.parseTimeZoneString(zone);
            } else {
                timeZone = TimeZone.getDefault();
            }
            if (scheduleLock.haslock) {
                this.registrar.addCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone)));
                logger.info("######类：" + bean.getClass().getName() + ",方法：" + method.getName() + "注册为调度任务，cron: " + cron);
            }

            // At this point we don't need to differentiate between initial delay set or not anymore
            if (initialDelay < 0) {
                initialDelay = 0;
            }

        } catch (IllegalArgumentException ex) {
            throw new IllegalStateException("Encountered invalid @CronScheduled method '" + method.getName() + "': " + ex.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void destroy() {
        this.registrar.destroy();
    }

    /**
     * 清空所有注册
     */
    public void reRegisterDestroy() {
        this.registrar.destroyJobs();
        this.registrar.setCronTasksList(new ArrayList<>());
        this.isstart = false;
    }

    /**
     * 重新注册
     */
    public void reRegister() {
        try {
            if (!isstart) {
                logger.info(" xxxxxx 没有启动，需要启动");
                this.registrar.afterPropertiesSet();
                isstart = true;
                logger.info(" xxxxxx 没有启动，启动完成");
            } else {
                logger.info(" 已经启动，暂时不要启动");
            }
        }  catch (Exception e) {
            logger.error(" xxxxxx启动失败，未知原因，需要别人代替", e);
            scheduleLock.startBad = true;
            isstart = false;
        }
    }
}
